/**
 * \file
 *
 * Test the bitmap method of documenting block allocation.
 */
#include <algorithm>
#include <stdint.h>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <set>
#include <string>
#include <sstream>
#include <vector>  

using std::cerr;  using std::cin;  using std::cout;  using std::endl;
using std::ios;  using std::setw;
using std::set; using std::string;  using std::vector;
using std::istringstream;

uint32_t const blockNumBytes = 4096;
uint8_t block[blockNumBytes];
uint32_t* const bitmap = reinterpret_cast<uint32_t*>(block);

uint32_t disk_allocate();
void disk_free(uint32_t block);


int main()
{
    memset(block, 0, blockNumBytes);
    typedef set<uint32_t> Set;
    Set blocks;
    
    for(;;)
    {
        string line;
        getline(cin, line);
        if(cin.rdstate() != ios::goodbit) //& (ios::failbit | ios::badbit | ios::eofbit))
            break;

        istringstream ss(line);
        string cmd, arg;
        ss >> cmd;
        if(ss.rdstate() & (ios::failbit | ios::badbit))
        {
            printf("Syntax error. Commands are a, d, l.\n");
            continue;
        }

        if(cmd == "a")
        {
            uint32_t block = disk_allocate();
            printf("Allocated block %u\n", block);
            Set::const_iterator i = blocks.find(block);
            if(i == blocks.end())
            {
                blocks.insert(block);
            }
            else
            {
                fprintf(stderr,"Test failure: block %u was previously allocated\n", block);
            }
        }
        else if(cmd == "d")
        {
            uint32_t block;
            ss >> block;
            if(ss.rdstate() & (ios::failbit | ios::badbit))
            {
                printf("Missing argument to \"deallocate\"\n");
                continue;
            }
            Set::iterator i = blocks.find(block);
            try
            {
                disk_free(block);
                if(i != blocks.end())
                {                
                    blocks.erase(i);
                    printf("Freed block %u\n", block);
                }
                else
                {
                    fprintf(stderr,"Test failure: deallocated block %u which was already free\n", block);
                }
            }
            catch(std::exception&)
            {
                if(i != blocks.end())
                {
                    fprintf(stderr,"Test failure: failed to deallocated block %u\n", block);
                }
                else
                {
                    printf("Error: block %u is not allocated\n", block);
                }
            }
        }
        else if(cmd == "l")
        {
            printf("List:");
            for(Set::const_iterator i = blocks.begin(); i != blocks.end(); ++i)
            {
                printf(" %u", *i);
            }
            putchar('\n');
        }
    }
    return 0;    
}


uint32_t disk_allocate()
{
    for(uint32_t i = 0; i < blockNumBytes/sizeof(uint32_t); ++i)
    {
        uint32_t* const word = bitmap + i;
        for(uint32_t j = 0; j < sizeof(uint32_t)*8; ++j)
        {
            uint32_t mask = 1 << j;
            if((*word & mask) == 0)
            {
                printf("%s: (%2u,%2u)\n", __FUNCTION__, i,j);
                *word |= mask;
                uint32_t diskBlockNumber = (i << 5) | j;
                return diskBlockNumber;
            }
        }
    }
    throw std::runtime_error("disk_allocate: exhausted");
}


void disk_free(uint32_t block)
{
    uint32_t i = block >> 5;
    uint32_t j = block & ((1 << 5) - 1);
    printf("%s: (%2u,%2u)\n", __FUNCTION__, i,j);
    uint32_t mask = 1 << j;
    if((bitmap[i] & mask) == 0)
        throw std::runtime_error("disk_free: block is not allocated");
    bitmap[i] &= ~mask;
}

